#include "../../include/ui/mainwindow.h"
#include "../../include/utils.h"

#include <bits/types/wint_t.h>
#include <bits/stdc++.h>
#include <ncurses.h>
#include <curses.h>
#include <string>

#define SCREEN_MAIN 0

#define KKEY_DOWN 258
#define KKEY_UP 259
#define KKEY_ESC 27
#define KEY_q 113

void MainWindow::moveDown() { // moveUp old
	this->currentItem--;
	if (this->currentItem < 0) this->currentItem = 0;
}

void MainWindow::moveUp() { //moveDown old
	this->currentItem++;
	int size = this->currentItems.size()-1;
	if (this->currentItem >= size) this->currentItem = size;
}

vector<string> MainWindow::getVisibleItems() {
	int start   = this->scrollTop;
	int end     = this->scrollTop + this->maxRows;
	int cline   = this->currentItem + 1;

	int sdelim  = this->indent - (cline - start - 1);
	int edelim  = this->indent - (end - cline);

	if (sdelim > 0) {
        start   -= sdelim;
        end     -= sdelim;
    }
	if (edelim > 0) {
        end     += edelim;
        start   += edelim;
    }

	if (start < 0) {
        start   = 0; 
        end     = this->maxRows;
    }

	this->currentItems.clear();
    for (int i=0; i <= this->menuItems.size()-1; i++) {
        string itemName = this->menuItems[i];
        for (int i=0; i < itemName.length()-1; i++) {
            itemName[i] = tolower(itemName[i]);
        }

        char delim = ' '; vector<string> out;
        split(this->searchQuery, delim, out);

        if (itemName.find(this->searchQuery) != string::npos
            ||
            itemName.find(out[0]) != string::npos) {
            this->currentItems.push_back(this->menuItems[i]);
        }
    }

	int isize = this->currentItems.size()-1;
	if (end > isize) {
		end = isize; start = end - this->maxRows;
	}

	if (start < 0) {start = 0;}

    vector<string> items;
    if (this->currentItems.empty() != true) {
	    for (int i=start; i <= end; i++) {
            items.push_back(this->currentItems[i]);
        }
    }

	this->scrollTop = start;

	return items;
}

void MainWindow::toggleMode() {
    if (this->isSearch) {
        this->isSearch = false;
    } else {
        this->isSearch = true;
    }
}

void MainWindow::turnStdMode() {
    this->isSearch = false;
}

string MainWindow::searchKeys() {
    if (this->lastKey == KEY_UP) {this->moveUp(); return "";}
    if (this->lastKey == KEY_DOWN) {this->moveDown(); return "";}

    if (this->lastKey == KEY_ENTER || this->lastKey == '\n' || this->lastKey == '\r') {
        return this->currentItems[this->currentItem];
    } else if (this->lastKey == KKEY_ESC) {
        this->turnStdMode();
    }

    else if (this->lastKey == '\t') {
        this->searchQuery = this->currentItems[this->currentItem] + " ";
    }

    else if (this->lastKey == KEY_BACKSPACE) {
        if (!this->searchQuery.empty()) {
            this->searchQuery.pop_back();
        }
    } else {
        this->currentItem = 0;
        this->searchQuery.push_back(this->lastKey);
    }

    return "";
}

string MainWindow::defaultKeys() {
    if (this->lastKey == KEY_ENTER || this->lastKey == '\n' || this->lastKey == '\r') {
        return this->currentItems[this->currentItem];
    } else if (this->lastKey == '/') {
        this->toggleMode();
    }

    if (this->lastKey == KEY_UP || this->lastKey == 'k') this->moveUp();
    if (this->lastKey == KEY_DOWN || this->lastKey == 'j') this->moveDown();

    if (lastKey == KEY_q) {this->terminated = true;}

    return "";
}

vector<string> MainWindow::process() {
	while (!terminated) {
		getmaxyx(this->win, this->maxY, this->maxX);
		this->maxRows = this->maxY - 3;

	    switch (this->screen) {
			default: this->drawMainScreen();
		}
        
        wint_t ch;
        get_wch(&ch);
		this->lastKey = ch;

        if (this->isSearch) {
            string result = this->searchKeys();
            if (!result.empty()) {return {result, searchQuery};}
        } else {
            string result = this->defaultKeys();
            if (!result.empty()) {return {result, searchQuery};}
        }

        switch (this->screen) {
            default: this->drawMainScreen();
        }

		usleep(1000);
	}

	return {"", ""};
}

void MainWindow::drawBody() {
	attrset(COLOR_PAIR(1));

	int cline = this->currentItem + 1;
	if (cline <= this->scrollTop) this->scrollTop = cline - 1;
	else if (cline - this->scrollTop > this->maxRows)
		this->scrollTop = cline - this->maxRows;

	this->visibleItems = this->getVisibleItems();
	int size = visibleItems.size();
	int index = this->scrollTop;
	int y = maxRows;

    string fullstr          = "";
    string beforeQueryPart  = "";
    string queryPart        = "";
    string afterQueryPart   = "";
    int queryLength         = 0;

	for (const auto &item : this->visibleItems) {
		if (this->currentItem == index) attrset(COLOR_PAIR(3));
		else attrset(COLOR_PAIR(1));

		this->clearLine(y, this->maxX);

        fullstr          = item;
        beforeQueryPart  = "";
        queryPart        = "";
        afterQueryPart   = "";

        if (this->currentItem == index)
            mvaddstr(y, 0, string(">").c_str());

        size_t found = fullstr.find(this->searchQuery);
        if (found != string::npos && this->searchQuery != "") {
            queryLength     = this->searchQuery.length();
            beforeQueryPart = fullstr.substr(0, found);
            queryPart       = fullstr.substr(found, queryLength);
            afterQueryPart  = fullstr.substr(found + queryLength, fullstr.length());

            mvaddstr(y, 2, beforeQueryPart.c_str());
            attron(A_BOLD);
            mvaddstr(y, 2+beforeQueryPart.length(), queryPart.c_str());
            attroff(A_BOLD);
            mvaddstr(y, 2+beforeQueryPart.length()+queryLength, afterQueryPart.c_str());
        } else { 
		    mvaddstr(y, 2, item.c_str());
        }
        index++; y--;
	}
}

void MainWindow::drawStatus() {
	attrset(A_BOLD | COLOR_PAIR(4));
	this->clearLine(this->maxY - 2, this->maxX);

    string adText = "";
    //if (!this->visibleItems.empty())
	//    adText = " | " + this->currentItems[this->currentItem] + " | "; 

    string curItemIndex = to_string(this->currentItem+1);
    string length = to_string(this->currentItems.size());

    if (length == "0") {curItemIndex = "0";}
	adText += curItemIndex + "/" + length;

    string sText = this->statusText + adText;

	mvaddstr(this->maxY-2, 0, sText.c_str());
}

void MainWindow::drawSearch() {
	attrset(COLOR_PAIR(1));

    if (!this->isSearch) return;
    string text = "> " + this->searchQuery;
    mvaddstr(this->maxY-1, 0, text.c_str()); 
}

void MainWindow::drawMainScreen() {
	attrset(COLOR_PAIR(1));
	for (int i = 0; i <= this->maxY; i++) this->clearLine(i, this->maxX);
	
	this->drawBody();
	this->drawStatus();
    this->drawSearch();

	curs_set(0);
	refresh();
}

void MainWindow::clearLine(int y, int l) {
	move(y, 1); l++;
	char str[l];
	for (int i = 0; i < l; i++) str[i] = ' ';
	str[l-1] = '\0';
	mvaddstr(y, 0, str);
}

vector<string> MainWindow::initWindow() {
    setlocale(LC_ALL, "");

    FILE* tty = fopen("/dev/tty", "r+");
    SCREEN* screen = newterm(NULL, tty, tty);
    set_term(screen);

	keypad(stdscr, true);
	nonl();
	cbreak();
	noecho();
    set_escdelay(25);

	this->win = newwin(0, 0, 0, 0);

	if (has_colors()) {
		start_color();
        use_default_colors();
		init_pair(1, COLOR_WHITE, -1);
		init_pair(2, COLOR_GREEN, COLOR_BLUE);
		init_pair(3, COLOR_BLACK, COLOR_CYAN);
        init_pair(4, COLOR_GREEN, -1);
	}

	this->currentItem = 0;
	this->statusText = &string(" ")[0];
    
	vector<string> result = this->process();

	endwin();

	return result;
}

MainWindow::MainWindow(vector<string> &items) {
	for (const auto &item : items) {
		this->menuItems.push_back(item);
	}
    
    this->currentItems = this->menuItems;
	this->lastItem = this->menuItems.size() - 1;
}




